package hotgammon.domain;
import java.util.*;

public class GameImp implements Game {
	
	private int[] dice = new int[2];
	private int rolls = 0;
	private Board board = new BoardImp();
	private Color firstPlayer;
	private Color secondPlayer;
	private ArrayList<GameListener> listeners = new ArrayList<GameListener>();
	private int movesLeft;
	private int[] diceLeft;
	private MoveStrategy ms;
	private DiceStrategy ds;
	private WinnerStrategy ws;
	
	
	public GameImp(GameFactory gf) {
		ds = gf.createDiceStrategy();
		ms = gf.createMoveStrategy();
		ws = gf.createWinnerStrategy();
	}
	
	private void throwDice() {
		dice = ds.throwDice(rolls);
		fireDiceChange();
		
		if(dice[0] == dice[1]) {
			diceLeft = new int[]{dice[0], dice[1], dice[0], dice[1]};
			movesLeft = 4;
		}
		else {
			diceLeft = new int[]{dice[0], dice[1]};
			movesLeft = 2;
		}
	}
	
	private void fireBoardChange() {
		for(GameListener gl : listeners)
			gl.boardChange();
	}
	
	private void fireDiceChange() {
		for(GameListener gl : listeners)
			gl.diceRolled();
	}
	
	public void newGame() {
		board.reset();
		rolls = 0;
		
		fireBoardChange();
		fireDiceChange();
	}
	
	public void nextTurn() {
		throwDice();
		if(rolls == 0 && dice[0] == dice[1])
			return;
		else if(rolls == 0) {
			firstPlayer = Color.BLACK;
			secondPlayer = Color.RED;
			if(dice[0] > dice[1]) {
				firstPlayer = Color.RED;
				secondPlayer = Color.BLACK;
			}
		}
		rolls++;	
	}
	
	public boolean move(Location from, Location to) {
		if(ms.move(board, this, from, to)) {
			board.move(from, to);
			movesLeft--;
			removeDistance(Math.abs(Location.distance(from, to)));
			
			fireBoardChange();
			return true;
		}
		
		return false;
	}
	
	/**
	private boolean isLegalMove(int dice, Location from) {
		Color colorCurrent = getPlayerInTurn();
		
		if(colorCurrent == Color.BLACK && getCount(Location.B_BAR) > 0) {
			for(Location to : board) {
				if(to.getIndex() >= Location.R1.getIndex() && to.getIndex() <= Location.R6.getIndex() && Math.abs(Location.distance(from, to)) == dice)
					return getCount(to) <= 1 || getColor(to) == Color.BLACK;
			}
		}
		else if(colorCurrent == Color.RED && getCount(Location.R_BAR) > 0) {
			for(Location to : board) {
				if(to.getIndex() >= Location.B1.getIndex() && to.getIndex() <= Location.B6.getIndex() && Math.abs(Location.distance(from, to)) == dice)
					return getCount(to) <= 1 || getColor(to) == Color.RED;
			}
		}
		else {
			for(Location to : board) {
				if(Location.distance(from, to) == dice && colorCurrent == Color.BLACK)
					return getCount(to) <= 1 || getColor(to) == Color.BLACK;
				else if(Location.distance(from, to) == -dice && colorCurrent == Color.RED)
					return getCount(to) <= 1 || getColor(to) == Color.RED;
			}
		}
		
		return false;
	}
	*/
	
	private boolean contains(int[] arr, int i) {
		for(int h : arr)
			if(h == i)
				return true;
		return false;
	}
	
	private void removeDistance(int dist) {
		boolean tmp = contains(diceLeft, dist);
		int len = diceLeft.length;
		int j = 0;
		if(tmp)
			len--;		
		int[] soFar = new int[len];
		
		for(int i : diceLeft)
			if(i == dist && tmp)
				tmp = false;
			else {
				soFar[j] = i;
				j++;
			}

		diceLeft = soFar;
	}
	
	public Color getPlayerInTurn() {
		if(rolls == 0)
			return Color.NONE;
		
		int turn = rolls % 2;
		if(turn == 1)
			return firstPlayer;
		else
			return secondPlayer;
	}
	
	public int getNumberOfMovesLeft() {
		return movesLeft;
	}
	
	public int[] diceThrown() {
		return dice;
	}
	
	public int[] diceValuesLeft() {
		return diceLeft;
	}
	
	public Color winner() {
		return ws.getWinner(this, rolls);
	}
	
	public Iterator<Location> boardIterator() {
		return board.iterator();
	}
	
	public Color getColor(Location location) {
		return board.getColor(location);
	}
	
	public int getCount(Location location) {
		return board.getCount(location);
	}
	
	public void addGameListener(GameListener gl) {
		listeners.add(gl);
	}
}
